package com.bubblegumproject.ogo.component;

import com.bubblegumproject.ogo.OgoException;
import net.sf.cglib.proxy.MethodProxy;
import org.apache.log4j.Logger;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * The invocation of a specific Component method.
 *
 * @author Azubuko Obele (buko.obele@gmail.com)
 */
public class ComponentInvocation {

    private Method method;

    private List<Interceptor> interceptors = new ArrayList<Interceptor>();

    private static final Logger log = Logger.getLogger(ComponentInvocation.class);

    protected ComponentInvocation(Method method) {
        assert (method != null);
        this.method = method;

        // process any interceptors that may be attached to the method. If the Method has any annotations that
        // represent the presence of an Interceptor we should create them now
        for (Annotation a : method.getAnnotations()) {
            Class<? extends Annotation> annotationClazz = a.annotationType();
            if (annotationClazz.isAnnotationPresent(InterceptorClass.class)) {
                Class interceptorType = annotationClazz.getAnnotation(InterceptorClass.class).value();
                Interceptor interceptor = createInterceptor(interceptorType);
                interceptor.initialize(a);
                interceptors.add(interceptor);
                log.debug(this + " added Interceptor " + interceptor + " to method " + method);
            }
        }
    }

    public Method getMethod() {
        return method;
    }

    public List<Interceptor> getInterceptors() {
        return interceptors;
    }

    /**
     * This method performs the component interception. A new InvocationContext is created and all interceptors and
     * other magic will occur before the actual method is invoked on the original (superclass) object. This method
     * should only be called by the {@link ComponentProxy} class. Nobody else
     * should ever touch it!
     */
    Object invoke(Object target, MethodProxy methodProxy, Object... arguments) throws Throwable {
        log.debug(this + " creating new InvocationContext to invoke " + methodProxy + " on " + target);
        InvocationContext invocationContext = new InvocationContext(this, target, methodProxy, arguments);
        return invocationContext.proceed();
    }

    /**
     * Invoke the component method via reflection. This reflected method call be forwarded to the proxy which is where
     * all the magic will happen.
     */
    public Object invoke(Object target, Object... arguments) {
        // in this case we DO NOT create an InvocationContext. We invoke the method normally via Reflection which will
        // be routed to the ComponentProxy which will create an InvocationContext if necessary
        try {
            return method.invoke(target, arguments);
        } catch (InvocationTargetException ie) {
           throw new ComponentInvocationException(ie.getCause());
        } catch (Exception e) {
            throw new OgoException(this + " failed to invoke component method", e);
        }
    }

    protected Interceptor createInterceptor(Class interceptor) {
        try {
            return (Interceptor) interceptor.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(this + " failed to instantiate Interceptor " + interceptor, e);
        }
    }
}
